package obj;
/** Imports */

import java.util.Vector;
import java.util.Collections;
import util.Globals;

/**
 * @author Jungpil Hahn 
 * @version 1.0
 */
public class Market {
    
	/** Data Members */
//	private Vector<Organization> market;
	private Vector market;

	// Constructor
    /**
     * 
     */
    public Market() {
//        market = new Vector<Organization>();
    	market = new Vector();
        initializeMarket();
    }
	
    // create orgs for the market 
    // org defs are in the globals
    // called by Market() constructor
    private void initializeMarket() {
    	int idx = 0; // org index
//    	System.out.println(Globals.numOrgs.length);
    	for (int i = 0; i < Globals.numOrgs.length; i++) { // loop through org types
    		for (int j = 0; j < Globals.numOrgs[i]; j++) {
//    			market.add(new Organization(idx, Globals.sensitivity, Globals.imitationStrategies[i]));
    			market.add(new Organization(Globals.sensitivity, Globals.imitationStrategies[i]));
    			idx++;
    		}
    	}
    	
    }
    
//	// add organization to market
//	public void add(Organization o) {
//		market.add(o);
//	}
	
	// remove n lowest performers with n new organizations
	public void replace() {
		if (Globals.numOrgsToReplace == 0) {
			// do nothing
		} else {
			// determine counts of new org types (by sensitivity) depending on numbers or strategy
			sort();
			int countNumbers[] = new int[Globals.numOrgTypes];
			double sumStrategy[] = new double[Globals.numOrgTypes];
			double totalStrategy = 0.0d;
			int numToReplace[] = new int[Globals.numOrgTypes];

			for (int i = Globals.numOrgsToReplace; i < market.size(); i++) {
				Organization o = (Organization)market.get(i);
				for (int j = 0; j < Globals.imitationStrategies.length; j++) {
					if (o.getImitationStrategy().equals(Globals.imitationStrategies[j])) {
						countNumbers[j]++;
						if (o.getStrategy() > 0) {
							sumStrategy[j] += o.getStrategy();
							totalStrategy += sumStrategy[j];
						}
					}
				} // END for j loop of sensitivities
			} // END for i loop of orgs (not to be replaced)
			
			// fill numToReplace array with number of new orgs by sensitivity / imitation strategy type
			int intReplace = 0;
			int remaining = 0;
			if (Globals.replaceBy.equals("numbers")) {
				for (int k = 0; k < countNumbers.length; k++) {
					numToReplace[k] = (int)((countNumbers[k] * Globals.numOrgsToReplace) / (market.size() - Globals.numOrgsToReplace));
					intReplace += numToReplace[k];
				}
				remaining = Globals.numOrgsToReplace - intReplace;
			} else if (Globals.replaceBy.equals("strategy")) {
				for (int k = 0; k < sumStrategy.length; k++) {
					numToReplace[k] = (int)((sumStrategy[k] * Globals.numOrgsToReplace) / totalStrategy);
					intReplace += numToReplace[k];
				}
				remaining = Globals.numOrgsToReplace - intReplace;
			}
			// remaining ones due to rounding / casting (int) error
			for (int r = 0; r < remaining; r++) {
				numToReplace[Globals.rand.nextInt(Globals.numOrgTypes)]++;
			}
			// numToReplace filled
			
			// now replace low performers (since sorted only need to loop from 0 to numOrgsToReplace)
			int idx = 0;
			for (int i = 0; i < Globals.numOrgTypes; i++) {
				for (int j = 0; j < numToReplace[i]; j++) {
					if (Globals.debug) {
						System.out.println("replacing: org (" + ((Organization)market.get(idx)).getIndex() + ")");
					}
					market.set(idx, new Organization(Globals.sensitivity, Globals.imitationStrategies[i]));
					idx++;
				}
			}
			
		}
	}

	// find organization of index x
	public Organization getOrg(int index) {
		boolean found = false;
		int i = 0;
		while (!found) {
			if (i >= market.size()) {
				break;
			} else {
				Organization o = (Organization)market.get(i); 
				if (index == o.getIndex()) {
					found = true;
					break;
				} 
				i++;
			}
		}
		if (found) {
			return (Organization)market.get(i);
		} else {
			return null;
		}
	}
	
	// sorts the market by performance asc (i.e., low performers first ~ high performers)
	private void sort() {
		Collections.sort(market);
		Collections.reverse(market);
	}
		
	public int size() {
		return market.size();
	}
	
	// each org in the market plans next period strategy
	// basically sets decision to change (2) or keep (1)
	// to do so, the org needs to know aspiration (based on perf at t-1) and performance (t-1)
	public void plan() {
		for (int i = 0; i < market.size(); i++) {
			Organization focal = (Organization)market.get(i);
			setSocialAspiration(focal); // determines aspiration based on other orgs' performance
		}
		// at this point since execute reset decision as 0, org.plan() sets decision (for next round) to 2 or 1
		for (int i = 0; i < market.size(); i++) {
			if (Globals.debug) { System.out.println("plan org (" + ((Organization)market.get(i)).getIndex() + ")"); }
			((Organization)market.get(i)).plan();
		}
	}
	
	// each org in the market executes next period strategy
	// sets aspiration (immediate past perf), strategy (if change), current performance
	public void execute() {
		for (int i = 0; i < market.size(); i++) {
			if (Globals.debug) { System.out.println("execute org (" + ((Organization)market.get(i)).getIndex() + ")"); }
			((Organization)market.get(i)).execute();
		}
	}
		

	
	// the market calculates each org's social aspiration level and gives it to the org
	private void setSocialAspiration(Organization o) {
		if (Globals.debug) { System.out.println("set social asp for org (" + o.getIndex() + ")"); }

		double aspiration; // return value (or value to set to individual orgs o)
		String potentialRefGroupIdx = ""; // indices of potential reference group members
		o.setPotentialRefGroupSize(0); // reset potentialRefGroupSize
		o.setPotentialRefGroupIdx(potentialRefGroupIdx); // reset org's potentialRefGroupIdx
//		Vector<Organization> potentialRefGroup = new Vector<Organization>(); // placeholder for orgs what are in reference group
		Vector potentialRefGroup = new Vector(); // placeholder for orgs what are in reference group
		String refGroupIdx = ""; // indices of reference group members (only up to AttentionLimit of potential reference group members)
		o.setRefGroupSize(0); // reset org's refgroupsize
		o.setRefGroupIdx(refGroupIdx); // reset org's RefGroupIdx
//		Vector<Organization> refGroup = new Vector<Organization>();
		Vector refGroup = new Vector();
		int idx = o.getIndex(); // focal org's index

		/* GET POTENTIAL REFERENCE GROUP */
		for (int i = 0; i < market.size(); i++) { // loop through all orgs in market
			Organization other = (Organization)market.get(i);
			if (idx != other.getIndex()) { // only relevant are OTHER orgs
				if (o.getSimilarity(other) >= Globals.similarityThreshold) { // other org is similar enough
					potentialRefGroup.add(other);
					potentialRefGroupIdx += other.getIndex() + ",";
//				} else {
//					nonrefGroup.add(other); // do we need this?
				}
			}
		} // END for i = 0 .. market.size (i.e., all other orgs)
		o.setPotentialRefGroupSize(potentialRefGroup.size());
		o.setPotentialRefGroupIdx(potentialRefGroupIdx);
		
		/* GET REFERENCE GROUP (RANDOMLY CHOOSE MAX(ATTENTIONLIMIT) ORGS FROM POTENTIALREFGROUP */
		// pick actual reference group from potential ref group and then set org's social aspiration level 
		// at this stage we should have divided other orgs into the reference group vector 
		// **initialize
		double sumSimilarity = 0.0d;
		double sumSimilarityPerf = 0.0d;
		int m = Math.min(Globals.attentionLimit, potentialRefGroup.size());
		if (m == 0) { // no orgs in reference group
			// do nothing since execute already set aspiration to immediate past performance
			//aspiration = o.getAspiration(); // immediate past (CHECK) performance
		} else {
			for (int j = 0; j < m; j++) {
				int r = Globals.rand.nextInt(potentialRefGroup.size());
				Organization rr = (Organization)potentialRefGroup.get(r);
				refGroupIdx += rr.getIndex() + ",";
				potentialRefGroup.remove(r);  // just so that org r is not picked again in computing aspiration
				refGroup.add(rr);
				sumSimilarity += Math.pow(o.getSimilarity(rr), o.getSensitivity());
				sumSimilarityPerf += (rr.getPerformance() * sumSimilarity);
			}
			o.setRefGroupIdx(refGroupIdx);
			o.setRefGroupSize(m);
			aspiration = sumSimilarityPerf / sumSimilarity;
			o.setAspiration(aspiration);
			
			
			/* FOR IMITATION */
			/*
			// now let's set the reference trait vector to use for imitation and set it
			// [TODO] CHECK WHAT TO DO IF REFGROUP IS EMPTY
			
			if (o.getImitationStrategy().equals("best")) {
				// best performer
				Collections.sort(refGroup);
//				o.setBestPerformerTraits(((Organization)refGroup.get(0)).getTrait());
				
			} else if (o.getImitationStrategy().equals("dominant")) {
				// dominant
				int[][] dominantTraits = new int[Globals.numTraits][Globals.numTraitValues];
				double myPerformance = o.getPerformance();
				for (int k = 0; k < refGroup.size(); k++) { // loop through all orgs in reference group set
					Organization ref = (Organization)refGroup.get(k);
					if (ref.getPerformance() > myPerformance) { // the ref group org is better than me -> tally dominant traits;
						int[] refTrait = ref.getTrait();
						for (int t = 0; t < Globals.numTraits; t++) {
							dominantTraits[t][refTrait[t]]++; 
						}
					}
				} // now dominantTraits should be set with counts 
								
				String[] dominantTraitsString = new String[5];
				for (int nt = 0; nt < Globals.numTraits; nt++) { dominantTraitsString[nt] = ""; } // initialize string
				for (int nt = 0; nt < Globals.numTraits; nt++) {
					int max = -1;
					for (int ntv = 0; ntv < Globals.numTraitValues; ntv++) {
						if (dominantTraits[nt][ntv] > max) {
							dominantTraitsString[nt] = ntv + ",";
							max = dominantTraits[nt][ntv];
						} else if (dominantTraits[nt][ntv] == max) {
							dominantTraitsString[nt] += ntv + ",";
						}	
					}
					max = -1;
				}
				
				int[] targetTraits = new int[Globals.numTraits];
				for (int nt = 0; nt < Globals.numTraits; nt++) {
					String[] result = dominantTraitsString[nt].split(",");
					int r = Globals.rand.nextInt(result.length); // randomly choose if multiple good traits
					targetTraits[nt] = Integer.parseInt(result[r]);
				} // targetTraits has been set
				
//				o.setReferenceTraits(targetTraits);
				
				
			} else if (o.getImitationStrategy().equals("random")) {
				// do nothing
			} else { // impossible => quit
				System.err.println("error: imitation strategy must be in [best, dominant, random]");
				System.exit(0);
			}
			*/
		}
	} // END setSocialAsp(org)
	
	private int[][] getReferenceTraits(String imitationStrategy) {
		int[][] refTraits = new int[Globals.numTraits][Globals.numTraitValues];
		if (imitationStrategy.equals("best")) {
			
		} else if (imitationStrategy.equals("dominant")) {
			
		} else if (imitationStrategy.equals("random")) {
			
		} else { // impossible
			System.err.println("error: imitation strategy must be in [best, dominant, random]");
			System.exit(0);
		}
		return refTraits;
	}
	
	// intermediate report of market (e.g., numOrgs by sensitivity; counts of strategy changes; average numNeighbors in refGroup; avg perf)
	// period is t; only needed for printout; not kept
	// prefix is either "before replace" or "after replace" to keep track of performance before and after replacement
	public void report(int period, String prefix) { 
		// stuff to track
		int[] numOrgs = new int[Globals.numOrgTypes];
		int[] numStrategyChange = new int[Globals.numOrgTypes];
		int[] sumNumNeighbors = new int[Globals.numOrgTypes];
		double[] sumPerformance = new double[Globals.numOrgTypes];
		double[] avgNumNeighbors = new double[Globals.numOrgTypes];
		double[] avgPerformance = new double[Globals.numOrgTypes];
		// get sums
		for (int i = 0; i < market.size(); i++) {
			Organization focal = (Organization)market.get(i);
			for (int j = 0; j < Globals.imitationStrategies.length; j++) {
				if (focal.getImitationStrategy().equals(Globals.imitationStrategies[j])) {
					numOrgs[j]++;
					if (focal.getDecision().equals("change")) {
						numStrategyChange[j]++;
					}
					sumNumNeighbors[j] += focal.getRefGroupSize();
					sumPerformance[j] += focal.getPerformance();
				}
			}
		}
		// calc averages
		for (int j = 0; j < Globals.imitationStrategies.length; j++) {
			if (numOrgs[j] == 0) {
				avgNumNeighbors[j] = Double.NaN;
				avgPerformance[j] = Double.NaN;
			} else {
				avgNumNeighbors[j] = (double)(sumNumNeighbors[j] / numOrgs[j]);
				avgPerformance[j] = (double)(sumPerformance[j] / numOrgs[j]);
			}
		}
		// print
		for (int j = 0; j < Globals.imitationStrategies.length; j++) {
			System.out.println(Globals.runID + "\t" + period + "\t" + prefix + Globals.imitationStrategies[j] + "\t" + numOrgs[j] + "\t" + numStrategyChange[j] + "\t" + avgNumNeighbors[j] + "\t" + avgPerformance[j]);
		}
	}
	
	public void reportDetails(int period) {
		for (int i = 0; i < market.size(); i++) {
			System.out.println(((Organization)market.get(i)).toString());
		}
	}
	
}